﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Consul;
using Micro.Extensions.Utilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Micro.Extensions.Bridge
{
    /// <summary>
    /// 服务管理
    /// </summary>
    public class ServiceManager : IServiceManager
    {
        private readonly ILogger _logger;
        private readonly IConsulClient _consulClient;
        private readonly IOptionsMonitor<ConsulConfig> _consulConfig;
        private readonly Timer _pollingTimer;
        private bool _polling;

        private readonly IHttpClientFactory _httpClientFactory;

        private readonly ConcurrentDictionary<string, IEnumerable<ServiceEntry>> _servicesLocalCache;
        private readonly ConcurrentDictionary<string, ServiceCounter> _dicServiceCounter;
        private readonly CancellationTokenSource _cancellationTokenSource;

        public ServiceManager(ILogger<ServiceManager> logger,
            IConsulClient consulClient,
            IOptionsMonitor<ConsulConfig> consulConfig,
            IHttpClientFactory httpClientFactory)
        {
            _cancellationTokenSource = new CancellationTokenSource();
            _servicesLocalCache = new ConcurrentDictionary<string, IEnumerable<ServiceEntry>>();
            _dicServiceCounter = new ConcurrentDictionary<string, ServiceCounter>();

            _logger = logger;
            _consulClient = consulClient;
            _consulConfig = consulConfig;
            _httpClientFactory = httpClientFactory;



            //主动刷新服务
            _pollingTimer = new Timer(x =>
            {
                if (_polling)
                {
                    return;
                }

                _polling = true;
                RefreshService();
                _polling = false;

            }, null, 3000, _consulConfig.CurrentValue.PollingInterval);

        }

        /// <summary>
        /// 获取Http客户端
        /// </summary>
        /// <param name="serviceName"></param>
        /// <param name="hashKey"></param>
        /// <returns></returns>
        public async Task<HttpClient> GetHttpClientAsync(string serviceName, string hashKey = null)
        {
            var serviceEntry = await GetServiceEntryAsync(serviceName, hashKey);
            return CreateHttpClient(serviceName.ToLower(), serviceEntry.Service.Address, serviceEntry.Service.Port);
        }

        /// <summary>
        /// 创建客户端
        /// </summary>
        /// <param name="serviceName">服务名</param>
        /// <param name="address">地址</param>
        /// <param name="port">端口</param>
        /// <returns></returns>
        private HttpClient CreateHttpClient(string serviceName, string address, int port)
        {
            var httpClient = _httpClientFactory.CreateClient(serviceName);
            httpClient.BaseAddress = new Uri($"http://{address}:{port}");
            httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
            httpClient.DefaultRequestHeaders.Add("Accept-Encoding", "Gzip");
            httpClient.DefaultRequestHeaders.Add("Accept-Charset", "UTF-8");
        
            _logger?.LogBridgeDebug( $"创建{serviceName}服务HttpClient实例：{address}：{port}");
            return httpClient;
        }

        /// <summary>
        /// 获取服务
        /// </summary>
        /// <param name="serviceName"></param>
        /// <param name="hashKey"></param>
        /// <returns></returns>
        public async Task<ServiceEntry> GetServiceEntryAsync(string serviceName, string hashKey = null)
        {

            //本地缓存查找 
            if (!_servicesLocalCache.TryGetValue(serviceName, out var healthServices))
            {
                healthServices = await GetHealthServicesAsync(serviceName);
                // ReSharper disable once PossibleMultipleEnumeration
                _servicesLocalCache.TryAdd(serviceName, healthServices);
            }
            // ReSharper disable once PossibleMultipleEnumeration
            if (!healthServices.Any())
            {
                _logger?.LogError($"未找到服务{serviceName}对应的注册节点，请检查该服务是否启动并注册到注册中心");
                throw new NullReferenceException($"未找到服务{serviceName}对应的注册节点，请检查该服务是否启动并注册到注册中心");
            }

            //轮询策略 
            var serviceCount = _dicServiceCounter.GetOrAdd(serviceName, new ServiceCounter { ServiceName = serviceName });
            // ReSharper disable once PossibleMultipleEnumeration
            var services = healthServices.ToArray();
            return services[serviceCount.Index++ % services.Length];
        }

        /// <summary>
        /// 获取服务信息
        /// </summary>
        /// <param name="serviceName"></param>
        /// <returns></returns>
        private async Task<IEnumerable<ServiceEntry>> GetHealthServicesAsync(string serviceName)
        {

            var response = (await _consulClient.Health.Service(serviceName.ToLower(), null, true, _cancellationTokenSource.Token).ConfigureAwait(false)).Response;

            //默认排除开发机器
            var result = response.Where(w => w.Service.Tags == null || w.Service.Tags.All(x => x != BridgeConst.DevModel));

            return result;
        }

        /// <summary>
        /// 主动刷新服务
        /// </summary>
        /// <returns></returns>
        private void RefreshService()
        {
            try
            {
                _logger?.LogDebug("开始刷新后台服务");
                var keys = _servicesLocalCache.Keys.ToArray();
                if (keys.Length == 0)
                {
                    _logger?.LogDebug("服务列表为空，直接返回");
                    return;
                }
                _logger?.LogDebug($"刷新服务列表{keys.ToJson()}");

                var tasks = new Task[keys.Length];
                for (var i = 0; i < keys.Length; i++)
                {
                    var key = keys[i];
                    tasks[i] = Task.Run(async () =>
                    {
                        try
                        {
                            var serviceLists = await GetHealthServicesAsync(key);
                            _servicesLocalCache[key] = serviceLists;
                            _logger.LogDebug($"刷新服务列表成功，Services:{serviceLists.ToJson()}");
                        }
                        catch (Exception e)
                        {
                            _logger.LogError(e, $"后台刷新服务 {key} 列表错误");
                        }
                    });
                }

                Task.WaitAll(tasks, _consulConfig.CurrentValue.PollingInterval - 100);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "后台刷新服务列表错误");
            }

        }

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            _cancellationTokenSource.Cancel();
            _consulClient.Dispose();
            _pollingTimer.Dispose();
            _logger?.LogBridgeDebug("Service Manager Dispose.");
        }
    }
}
